

package com.junjie.index12306.biz.ticketservice.canal;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.junjie.index12306.biz.ticketservice.common.constant.RedisKeyConstant;
import com.junjie.index12306.biz.ticketservice.common.enums.CanalExecuteStrategyMarkEnum;
import com.junjie.index12306.biz.ticketservice.common.enums.SeatStatusEnum;
import lombok.RequiredArgsConstructor;
import com.junjie.index12306.biz.ticketservice.mq.event.CanalBinlogEvent;
import com.junjie.index12306.framework.starter.cache.DistributedCache;
import com.junjie.index12306.framework.starter.designpattern.strategy.AbstractExecuteStrategy;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;

/**
 * 列车余票缓存 更新组件
 * 监听数据库 更改redis
 *
 */
@Component
@RequiredArgsConstructor
public class TicketAvailabilityCacheUpdateHandler implements AbstractExecuteStrategy<CanalBinlogEvent, Void> {

    private final DistributedCache distributedCache;

    /**
     * 核心功能是对数据库变更事件进行监听，当火车票的状态发生变化时（变为可用或锁定），
     * 同步更新Redis中存储的对应火车线路各站点之间的剩余票数信息
     */
    @Override
    public void execute(CanalBinlogEvent message) {
        // 存储变更后的新数据 条件是旧数据中seat_status不为空且其值在有效状态（AVAILABLE或LOCKED）内
        List<Map<String, Object>> messageDataList = new ArrayList<>();
        // 存储满足特定条件的变更前旧数据
        List<Map<String, Object>> actualOldDataList = new ArrayList<>();
        // 1、选出符合条件的 新/旧 数据
        for (int i = 0; i < message.getOld().size(); i++) {
            Map<String, Object> oldDataMap = message.getOld().get(i);
            if (oldDataMap.get("seat_status") != null && StrUtil.isNotBlank(oldDataMap.get("seat_status").toString())) {
                Map<String, Object> currentDataMap = message.getData().get(i);
                if (StrUtil.equalsAny(currentDataMap.get("seat_status").toString(), String.valueOf(SeatStatusEnum.AVAILABLE.getCode()), String.valueOf(SeatStatusEnum.LOCKED.getCode()))) {
                    actualOldDataList.add(oldDataMap);
                    messageDataList.add(currentDataMap);
                }
            }
        }
        if (CollUtil.isEmpty(messageDataList) || CollUtil.isEmpty(actualOldDataList)) {
            return;
        }
        // 存储需要更新Redis缓存的键值对及其对应的座位类型和数量增量
        Map<String, Map<Integer, Integer>> cacheChangeKeyMap = new HashMap<>();
        // 2、遍历新数据列表 对缓存中的余票数据进行增或减
        for (int i = 0; i < messageDataList.size(); i++) {
            Map<String, Object> each = messageDataList.get(i);
            Map<String, Object> actualOldData = actualOldDataList.get(i);
            // 2.1、获取旧数据状态 然后判断是 座位状态是 可售(+1) or 锁定(-1)
            String seatStatus = actualOldData.get("seat_status").toString();
            int increment = Objects.equals(seatStatus, "0") ? -1 : 1;
            String trainId = each.get("train_id").toString();
            // 2.2、构建需要改的hashKey
            String hashCacheKey = RedisKeyConstant.TRAIN_STATION_REMAINING_TICKET + trainId + "_" + each.get("start_station") + "_" + each.get("end_station");
            // 2.3、更新对应座位类型 需要更改的 对应的票数
            // 看Map中有没有，没有就新创建一个Map，有就在原有基础上累加数字
            Map<Integer, Integer> seatTypeMap = cacheChangeKeyMap.get(hashCacheKey);
            if (CollUtil.isEmpty(seatTypeMap)) {
                seatTypeMap = new HashMap<>();
            }
            Integer seatType = Integer.parseInt(each.get("seat_type").toString());
            Integer num = seatTypeMap.get(seatType);
            seatTypeMap.put(seatType, num == null ? increment : num + increment);
            cacheChangeKeyMap.put(hashCacheKey, seatTypeMap);
        }
        StringRedisTemplate instance = (StringRedisTemplate) distributedCache.getInstance();
        cacheChangeKeyMap.forEach((cacheKey, cacheVal) -> cacheVal.forEach((seatType, num) -> instance.opsForHash().increment(cacheKey, String.valueOf(seatType), num)));
    }

    @Override
    public String mark() {
        return CanalExecuteStrategyMarkEnum.T_SEAT.getActualTable();
    }
}
